/*
 * @Author: czy
 * @Date: 2021-04-04 22:39:40
 * @LastEditTime: 2021-05-01 09:00:55
 */
#include "io.h"
#include "vga.h"

static struct
{
    int16 *head;     // Head pointer.
    int16 *tail;     // Tail pointer.
    int16 *cursor;   // Cursor pointer.
    int16 *boundary; // The upper boundary of the output.
    int rows;        // Number of rows.
    int columns;     // Number of columns.
} vga = {(int16 *)vga_base, (int16 *)vga_base + ROW *COL, (int16 *)vga_base, (int16 *)vga_base, ROW, COL};

// Set cursor
static void reset_cursor(void)
{
    int num = vga.cursor - vga.head;
    int col_num, row_num;
    col_num = num % 256;
    row_num = num / 256;
    outb(index_port, col_port);
    outb(data_port, col_num);
    outb(index_port, row_port);
    outb(data_port, row_num);
}

// Set the upper boundary of the output.
void set_boundary(void)
{
    vga.boundary = vga.cursor;
}

// Clear the screen and return the cursor to its original value.
void clear_screen(void)
{
    for (int16 *p = vga.head; p < vga.tail; p++)
        *p = (int16)0x0700;
    vga.cursor = vga.head;
    set_boundary();
    reset_cursor();
}

// The screen scrolls down one line.
static void scrolling(void)
{
    if (vga.cursor >= vga.tail - COL + 1)
    {
        int16 *p = vga.head;
        int16 temp;
        for (; p < vga.tail - COL + 1 - vga.columns; p++)
        {
            temp = *(p + vga.columns);
            *p = temp;
        }
        for (int i = 0; i < vga.columns; i++)
        {
            *p = (int16)0x0700;
            p++;
        }
        vga.cursor = vga.tail - COL + 1 - vga.columns;
    }
}

// Control character: \n
static char newline(void)
{
    vga.cursor = vga.head + (((vga.cursor - vga.head) / NEWLINE) + 1) * NEWLINE;
    return '\n';
}

// Control character: \t
static char tab(void)
{
    vga.cursor = vga.head + (((vga.cursor - vga.head) / TAB) + 1) * TAB;
    return '\t';
}

// Control character: \b
static char backspace(void)
{
    vga.cursor--;
    return '\b';
}

// Control character: DEL
static char delete(void)
{
    if (vga.cursor > vga.boundary)
    {
        backspace();
        *vga.cursor = (int16)(0x07 << 8 | ' ');
        return 127;
    }
    else
    {
        return '\0';
    }
}

// Control character: \r
static char carriage(void)
{
    vga.cursor = vga.head + ((vga.cursor - vga.head) / NEWLINE) * NEWLINE;
    return '\r';
}

// Common characters
static char put_character(char ch, int color)
{
    *(vga.cursor) = (int16)(color << 8 | ch);
    vga.cursor++;
    return ch;
}

/**
 * @description: Output a character to the vga. (The cursor is not modified.)
 * @param c     Character to be output.
 * @param color Output color.
 */
static char outchar(char ch, int color)
{
    char c;
    switch (ch)
    {
    case '\n':
        c = newline();
        break;
    case '\t':
        c = tab();
        break;
    case '\b':
        c = backspace();
        break;
    case '\r':
        c = carriage();
        break;
    case 127:
        c = delete();
        break;
    default:
        c = put_character(ch, color);
        break;
    }
    return c;
}

/**
 * @description: Output a character to the vga.
 * @param c     Character to be output.
 * @param color Output color.
 */
char vga_put_char(char ch, int color)
{
    char c = outchar(ch, color);
    scrolling();
    reset_cursor();
    return c;
}

/**
 * @description: Output a string to VGA.
 * @param str   The first address of the string.
 * @param color Output color.
 */
void append2screen(char *str, int color)
{
    while (*str != '\0')
    {
        outchar(*str++, color);
        scrolling();
    }
    reset_cursor();
}

/**
 * @description: Outputs message at a specified location, such as the current time. 
 *                  When the output is finished, the cursor returns to its original position.
 * @param position  LEFT if 1 else RIGHT.
 * @param color     Output color.
 * @param msg       Message.
 */
void show_message(int position, int color, char *msg)
{
    int16 *temp = vga.cursor;
    if (position == 1)
    {
        vga.cursor = (int16 *)(vga.head + COL * (ROW - 1));
    }
    else
    {
        int len = 0;
        char *p = msg;
        while (*(p++) != '\0')
        {
            len++;
        }
        vga.cursor = (int16 *)(vga.head + COL * ROW - 1 - len + 1);
    }
    while (*msg != '\0')
    {
        outchar(*msg++, color);
    }
    vga.cursor = temp;
    reset_cursor();
}

/**
 * @description: Cursor offset.
 * @param offset    Offset.
 */
void vga_offset(int offset)
{
    vga.cursor = vga.cursor + offset;
    reset_cursor();
}
